package com.xm.spider;

import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class SeleniumTrans {
    /**
     * 线程池默认大小
     */
    private static final int THREAD_POOL_SIZE = 16;
    /**
     * 并发数
     */
    private static final int MAX_PAGES_TO_SCRAPE = 32;
    /**
     * 并行结果暂存
     */
    protected static List<Map<Integer, String>> resultList;
    /**
     * 处理翻译任务线程池
     */
    protected static ExecutorService scraperService;
    /**
     * 处理翻译结果转文件任务线程池
     */
    protected static ExecutorService toFileService;

    static {
        resultList = new ArrayList<>();
        for (int i = 0; i < 16*16; i++) {
            Map<Integer, String> resultMap = new ConcurrentSkipListMap<>();
            resultList.add(resultMap);
        }
        scraperService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
        toFileService = Executors.newFixedThreadPool(10);
    }

    /**
     * 执行单批次翻译段落最大长度（根据实际有道词典限制）
     */
    private static final int maxChunkSize = 5000;
//    private static final int maxChunkSize = -1;
    /**
     * 执行单批次翻译段落最大个数（根据实际并发限制）
     */
    private static final int partitionSize = MAX_PAGES_TO_SCRAPE;

    /**
     * 测试翻译pdf 并将翻译结果写入output.txt文件
     */
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        WebDriver webDriver = null;
        try {
            log.info("爬虫开始");
            Random random = new Random();

            //浏览器输入‘chrome://version/’查看谷歌版本：120.0.6099.71
            System.setProperty("webdriver.chrome.driver", "C:\\Program Files\\Google\\Chrome\\Application\\chromedriver.exe");

            ChromeOptions options = new ChromeOptions();
            options.addArguments("--headless");
            options.addArguments("--remote-allow-origins=*");
            //预反爬虫11：User-Agent伪装；
            options.addArguments("--user-agent=" + SeleniumExample.userAgents[random.nextInt(3)]);
            options.addArguments("--log-level=INFO");

            //读取pdf分页，并获取页数据
            //加载PDF文件
            PDDocument document = Loader.loadPDF(new File("D:\\data\\新建文件夹\\Who Goes There_Book.pdf"));
            //创建一个PDF文本提取器
            PDFTextStripper pdfTextStripper = new PDFTextStripper();
            //获取PDF中的文本内容
            String pdfText = pdfTextStripper.getText(document);
            //假设章节信息是通过查找 "Chapter" 关键字来识别
            String[] chapters = pdfText.split("Chapter");
            String[] chaptersNew = new String[chapters.length];
            for (int i = 0; i < chapters.length; i++) {
                if(i == 0){
                    chaptersNew[i] = chapters[i];
                }else{
                    chaptersNew[i] = "Chapter" + chapters[i];
                }
            }

            for (int i = 0; i < chapters.length; i++) {//循环，逐个章节翻译
                String text = chaptersNew[1];
                //为保证原文档换行样式不变，预自定义特殊字符 替换 换行符
                String[] textArr = text.split("\n \r");
                String[] textArrNew = new String[textArr.length];
                for (int j = 0; j < textArr.length; j++) {
                    if(j != textArr.length - 1){//将(nr)自定义标识替换为换行符
                        textArrNew[j] = textArr[j] + "(nr)";
                    }else{
                        textArrNew[j] = textArr[j];
                    }
                }

                //合并小于5000字符的段落，减少调用次数（可选）
                String[] paragraphArr;
                if(maxChunkSize != -1){//长段落翻译的快了一些（第一章耗时25s）
                    List<String> paragraphs = getMergeParagraphs(textArrNew);
                    paragraphArr = paragraphs.toArray(new String[0]);
                }else{//长段落翻译的快了一些（第一章耗时81s） 避免内存溢出，谨慎测试！！
                    paragraphArr = textArrNew;
                }

                //拼接后的长段落，按照最大线程数重新分组
                List<List<String>> chunks = getLongParagraphs(paragraphArr);

                //开始本章节的翻译。。
                log.info("爬虫并发执行中。。");
                List<Future<String>> futures = new ArrayList<>();
                AtomicInteger index = new AtomicInteger(0);
                for (int j = 0; j < chunks.size(); j++) {
                    for (String paragraph : chunks.get(j)) {//每五个一组
                        //预反爬虫33：等待随机0~50ms
                        Thread.sleep((long) (Math.random()*50));
                        Future<String> future = (Future<String>) scraperService.submit(new ScraperTask(i, index.getAndIncrement(), paragraph, options));
                        futures.add(future);
                    }
                }

                //异步等待并发执行的处理结果
                String folder = "D:\\data\\新建文件夹\\splitChapter";
                String filePath = folder + "\\Chapter_" + (i + 1) + ".pdf";
                List<Future<String>> futuresFile = new ArrayList<>();
                Future<String> future = (Future<String>) toFileService.submit(new Trans2PdfTask(i, PDFUtil.font, filePath, folder, futures));
                futuresFile.add(future);
                waitFutures(futuresFile);

//                if(i == 3){//测试打印前三章
//                    break;//TEST
//                }
                break;//TEST
            }

            //TODO 合并所有章节的pdf 完成翻译（可选）

            log.info("爬虫正在关闭。。");
        } catch (InterruptedException | IOException e) {
            throw new RuntimeException(e);
        } finally {
            log.info("爬虫结束。耗时={}s", (System.currentTimeMillis() - start)/1000L);
            scraperService.shutdown();
            toFileService.shutdown();
        }
    }

    /**
     * 翻译结果写入txt文件
     * @param folder 文件存储目录
     * @param filePath 生成文件名称
     * @param content 写入文本内容
     */
    public static void transToTxt(String folder, String filePath, String content){
        File tmpFolder = new File(folder);
        try {
            if(!tmpFolder.exists()){//创建临时文件夹
                Files.createDirectory(tmpFolder.toPath());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write(content);
        } catch (IOException e) {
            log.error("爬虫将翻译结果写入txt文件异常！", e);
        }
    }

    /**
     * 翻译结果写入pdf文件
     * @param font 字体文件
     * @param folder 文件存储目录
     * @param filePath 生成文件名称
     * @param content 文本内容
     */
    public static void transToPdf(File font, String folder, String filePath, String content){
        PDFUtil.newPdf(font, folder, filePath, content);
    }

    /**
     * 等待并发执行结果
     * @param futures 并发执行结果
     */
    public static void waitFutures(List<Future<String>> futures){
        for (Future<String> future : futures) {
            try {
                String result = future.get();
                log.info("爬虫执行结果={}", result);
            } catch (InterruptedException | ExecutionException e) {
                log.info("爬虫执行异常！", e);
            }
        }
    }

    /**
     * 获取页数等待的翻译结果
     * @param page 页数
     * @return 翻译结果
     */
    public static String getContent(int page){
        //循环resultMap，获取翻译结果
        StringBuilder builder = new StringBuilder();
        Map<Integer, String> resultMap = SeleniumTrans.resultList.get(page);
        if(!resultMap.isEmpty()){
            resultMap.forEach((key, value) -> builder.append(value));
        }
        String content = builder.toString();
        if(content.isEmpty()){
            log.error("爬虫未翻译到任何内容！");
        }
        return content;
    }

    /**
     * 获取最大长度的合并段落拆分
     * @param textArrNew 长文本
     */
    private static List<String> getMergeParagraphs(String[] textArrNew) {
        List<String> paragraphs = new ArrayList<>();
        StringBuilder currentChunk = new StringBuilder();
        for (String paragraph : textArrNew) {
            if (currentChunk.length() + paragraph.length() > maxChunkSize) {
                //如果会超过，则将当前块添加到结果列表，并重置当前块
                paragraphs.add(currentChunk.toString());
                currentChunk.setLength(0); // 清空当前块
            }
            currentChunk.append(paragraph);
        }
        //添加最后一个未满的块
        if (currentChunk.length() > 0) {
            paragraphs.add(currentChunk.toString());
        }
        return paragraphs;
    }

    /**
     * 获取批量并行的长段落拆分
     * @param paragraphArr 最大长度的合并段落数组
     */
    private static List<List<String>> getLongParagraphs(String[] paragraphArr){
        return PDFUtil.groupSize(paragraphArr, partitionSize);
    }

}
